package com.bosssoft.gateway.authorization.serve.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bosssoft.gateway.authorization.dao.entity.Resource;
import com.bosssoft.gateway.authorization.dao.entity.RoleResource;
import com.bosssoft.gateway.authorization.dao.mapper.ResourceMapper;
import com.bosssoft.gateway.authorization.dao.mapper.RoleResourceMapper;
import com.bosssoft.gateway.authorization.serve.service.AuthorizationService;
import com.bosssoft.gateway.authorization.serve.service.RenewTokenService;
import com.bosssoft.gateway.authorization.serve.service.constant.BusinessResultConsts;
import common.util.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import tk.mybatis.mapper.entity.Example;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.regex.Pattern.compile;

/**
 * @className: AuthorizationServiceImpl
 * @description: 解决什么问题：
 * 演示线程池的正确使用
 * 主要方法：
 * methodxx 使用 ThreadPoolExecutor创建线程进行任务操作
 * 注意事项：
 * 例如：该类需要从容器获取不可以new
 * @author: lanyukai
 * @date: 2024/6/18 23:19
 * @since 1.0
 **/
@Slf4j
@Service
public class AuthorizationServiceImpl implements AuthorizationService {

    @Autowired
    private ResourceMapper resourceMapper;
    @javax.annotation.Resource(name = "roleResourceRedisTemplate")
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    RoleResourceMapper roleResourceMapper;
    @Autowired
    private RenewTokenService renewTokenService;
    private static final String AUTHOR_REQUEST_URL_EXPR = "^https?:\\/\\/(?:[0-9a-zA-Z:\\.]*)([^\\?]*)";
    //    @Value("${app.secret.token.authorization.strategy}")
    private Integer authorizationStrategy = 0;

    /**
     * 鉴权判断用户访问的uri是否在token定义中
     * 实现方法：
     * 1 首先判断 token是否过期或者即将过期
     * 2 解析token 得到角色
     * 3 通过角色从 redis取得 角色对应的资源，如果redis没有 则调用用户中心获取资源列表，
     * 4 将访问的url和资源匹配，判断是否符合，如果符合则返回true通过 否则不通过返回false
     *
     * @param token 用户的token
     * @param url
     * @return 参考 BusinessResultConstants 说明
     */
    @Override
    public Integer roleAuthorization(String token, String url) {

        Assert.hasText(token, "token为空 roleAuthorization 参数异常");
        Assert.hasText(url, "url为空 roleAuthorization 参数异常");

        //解析token的 payload部分
        Claims joPayload = JwtUtil.parseToken(token);
        String string = joPayload.get("token").toString();
        System.out.println(string);
        // token 过期判断
        int tokenExpireResult = renewTokenService.expire(Long.parseLong(joPayload.get("token").toString().split("=")[1].split(",")[0]), token);
        if (BusinessResultConsts.TOKEN_EXPIRE_TURE == tokenExpireResult) {
            return BusinessResultConsts.AUTHOR_RESULT_TOKEN_EXPIRE;
        } else if (BusinessResultConsts.TOKEN_EXPIRE_NEARLY == tokenExpireResult) {
            return BusinessResultConsts.AUTHOR_RESULT_TOKEN_NEARLY;
        }

        // 如果token没有过期则根据角色获取资源列表
        List<Long> roleIdList = extractRoleListIds(joPayload.get("token").toString());

        /**
         * 读取nacos配置文件中的策略
         * 0：完全放行
         * 1：鉴权
         * 2：部分放行
         */
        if (authorizationStrategy == BusinessResultConsts.AUTHOR_STRATEGY_ALLOW) {
            // code == 0：直接放行
            log.info("放行");
            return BusinessResultConsts.AUTHOR_RESULT_TRUE;
        } else if (authorizationStrategy == BusinessResultConsts.AUTHOR_STRATEGY_FULL_APPLY) {
            // code == 1：鉴权
            List<Resource> resourceList = getResourcesByRoleIds(roleIdList);
            return matchRequestUrlWithResources(getMatchURL(url), resourceList)
                    ? BusinessResultConsts.AUTHOR_RESULT_TRUE
                    : BusinessResultConsts.AUTHOR_RESULT_MATCH_RESOURCE_FALSE;
        } else {
            return BusinessResultConsts.AUTHOR_RESULT_TRUE;
        }
    }

    /**
     * 根据角色从缓存或者远程接口取得资源id
     * 从缓存先取得 如果没有则从数据库取得 如果没有则从用户中心接口取得
     *
     * @param roleIds 角色数组
     * @return 角色数组所包含的资源id
     */
    @Override
    public List<Resource> getResourcesByRoleIds(List<Long> roleIds) {
        List<Resource> resourceList = getRoleResourceFromRedis(roleIds);
        if (!CollectionUtils.isEmpty(resourceList)) {
            return resourceList;
        }
        resourceList = getRoleResourceFromDB(roleIds);
        if (!CollectionUtils.isEmpty(resourceList)) {
            return resourceList;
        }
        return null;
    }

    /**
     * 匹配用户访问的url 和 用户角色所拥有的资源列表的
     *
     * @param url       访问的url
     * @param resources 角色所拥有的资源列表
     * @return true成功 false失败
     */
    @Override
    public boolean matchRequestUrlWithResources(String url, List<Resource> resources) {
        for (Resource resource : resources) {
            if (!StringUtils.isEmpty(resource.getApi())) {
                final Pattern pattern = compile(String.format("%s$", resource.getApi()));
                final Matcher matcherApi = pattern.matcher(url);
                // 匹配成功
                if (matcherApi.find()) {
                    return Boolean.TRUE;
                }
            }
        }
        // 没有匹配到则返回false
        return Boolean.FALSE;
    }

    /**
     * 查询请求的url是否在通用的资源列表中
     *
     * @param url 请求的url
     * @return true 在列表 false不在
     */
    @Override
    public Boolean commonResource(String url) {
        final Pattern compile = compile(AUTHOR_REQUEST_URL_EXPR);
        Matcher matcher = compile.matcher(url);
        if (matcher.find()) {
            url = matcher.group(1);
        }
        List<Resource> commonResources = getCommonResource();

        if (!CollectionUtils.isEmpty(commonResources)) {
            for (Resource resource : commonResources) {
                if (!StringUtils.isEmpty(resource.getApi())) {
                    final Pattern pattern = compile(String.format("%s$", resource.getApi()));
                    final Matcher matcherApi = pattern.matcher(url);
                    if (matcherApi.find()) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private List<Resource> getCommonResource() {
        List<Resource> commonResources = new ArrayList<>();
        try {
            //status=2当做缓存的key
            redisTemplate.opsForValue().get(String.valueOf(2));
            Collection<Object> collection = redisTemplate.opsForHash().entries(BusinessResultConsts.HASH_COMMON_RESOURCE_KEY).values();
            //获取全部的通用资源
            if (!CollUtil.isEmpty(collection)) {
                commonResources = JSON.parseArray(String.valueOf(collection), Resource.class);
            }
        } catch (Exception e) {
            log.error("通用资源缓存获取失败");
            throw new RuntimeException("通用资源缓存获取失败", e);
        }
        if (CollectionUtils.isEmpty(commonResources)) {
            commonResources = resourceMapper.commonResource();
        }
        return commonResources;
    }

    private List<Resource> getRoleResourceFromRedis(List<Long> roleIds) {
        Assert.notEmpty(roleIds, "roleIdsList不能为空");
        Assert.notEmpty(roleIds, "roleIds不能为空");
        /**
         * 存储多个角色的资源综合的 Map<Long, RoleResource>参考如下使用，keylong为role_resource表的行id
         *  我们要的数据是 RoleResource
         */

        Set<Map<Long, RoleResource>> mapSetRoleResource = new HashSet<>();
        // 返回一个角色对应的资源map并且添加到set，例如两个角色这个 这个set就2个元素
        roleIds.stream().forEach(id -> {
            String hashKey = BusinessResultConsts.HASH_ROLE_ID_KEY + id;
            Map roleResourceMap = redisTemplate.opsForHash().entries(hashKey);
            mapSetRoleResource.add(roleResourceMap);
        });
        /**
         * 找出每个角色对应的资源列表的具体的资源信息 使用set是为了避免重复
         * 首先遍历set中每个角色，取得map 然后遍历map将其中的resource取得增加到 resourceSet
         */

        Set<Resource> resourceSet = new HashSet<>();
        mapSetRoleResource.stream().forEach(map -> {
            for (Object roleResource : map.values()) {
                // 如果缓存和数据库都没取到该返回值为 new Resource
                String roleResourceJsonStr = JSONObject.toJSONString(roleResource);
                try {
                    RoleResource roleResourceTarget = JSONObject.parseObject(roleResourceJsonStr, RoleResource.class);
                    Resource resource = getResourceById(roleResourceTarget.getResourceId());
                    resourceSet.add(resource);
                } catch (Exception ex) {
                    log.error("JSONObject.parseObject 发生错误:" + ex.getMessage(), ex);
                }


            }
        });
        return new ArrayList<>(resourceSet);
    }

    private List<Resource> getRoleResourceFromDB(List<Long> roleIds) {
        Example example = new Example(RoleResource.class);
        example.createCriteria().andIn("roleId", roleIds);
        // 查询角色所包含的资源id集合
        List<RoleResource> roleResourceList = roleResourceMapper.selectByExample(example);
        //将资源ｉｄ集合转为resource对象集合
        List<Resource> resourceList = new ArrayList<>();
        roleResourceList.stream().forEach(roleResource -> resourceList.add(getResourceById(roleResource.getResourceId())));
        return resourceList;
    }

    public Resource getResourceById(Long id) {
        Object resource = redisTemplate.opsForValue()
                .get(BusinessResultConsts.RESOURCE_ID_KEY + id);
        if (resource != null) {
            return JSONObject.parseObject(JSONObject.toJSONString(resource), Resource.class);
        }
        resource = resourceMapper.selectByPrimaryKey(id);
        return resource == null ? null : new Resource();
    }

    private String getMatchURL(String url) {
        Assert.hasText(url, "url NOT NULL");
        Assert.hasText(AuthorizationServiceImpl.AUTHOR_REQUEST_URL_EXPR, "url NOT NULL");
        String newUrl = "";
        final Pattern compile = compile(AuthorizationServiceImpl.AUTHOR_REQUEST_URL_EXPR);
        Matcher matcher = compile.matcher(url);
        if (matcher.find()) {
            newUrl = matcher.group(1);
        }
        return newUrl;
    }

    public List<Long> extractRoleListIds(String input) {
        List<Long> roleIds = new ArrayList<>();

        // 使用正则表达式 提取 roleList 部分
        Pattern roleListPattern = Pattern.compile("roleList=\\[(.*?)\\]");
        Matcher roleListMatcher = roleListPattern.matcher(input);

        if (roleListMatcher.find()) {
            String roleListString = roleListMatcher.group(1);

            // 使用正则表达式 提取 roleList 中的每个 id
            Pattern idPattern = Pattern.compile("id=(\\d+)");
            Matcher idMatcher = idPattern.matcher(roleListString);

            while (idMatcher.find()) {
                long id = Long.parseLong(idMatcher.group(1));
                roleIds.add(id);
            }
        }

        return roleIds;
    }
}
